package it.aekidna.cleverplatypus.workflow
{
   
   
    import as3reflect.ClassUtils;
    
    import flash.events.EventDispatcher;
    import flash.net.registerClassAlias;
    import flash.utils.getQualifiedClassName;
    
    import it.aekidna.cleverplatypus.mediator.MetadataUtil;
    import it.aekidna.cleverplatypus.utils.IAutowirable;
    
    import mx.logging.ILogger;
    import mx.logging.Log;
    import mx.utils.ObjectUtil;
   
    [Event(
        name="started",
        type="it.aekidna.cleverplatypus.workflow.WorkflowEvent")]
    [Event(
        name="warning",
        type="it.aekidna.cleverplatypus.workflow.WorkflowEvent")]
    [Event(
        name="fault",
        type="it.aekidna.cleverplatypus.workflow.WorkflowEvent")]
    [Event(
        name="completed",
        type="it.aekidna.cleverplatypus.workflow.WorkflowEvent")]

   
    [DefaultProperty("tasks")]
    public class Workflow 
    	extends EventDispatcher 
    	implements IWorkflow, IAutowirable
    {
       
        protected static const LOGGER : ILogger =
            Log.getLogger( getQualifiedClassName( Workflow ).replace(/:+/g, "." ) );

        public static const PARALLEL : String = "parallel";
        public static const SERIAL : String = "serial";

			[Bindable]
			public var completedTasksCount : uint = 0;
		
        protected var _flow : String = SERIAL;
       
        protected var _enabled : Boolean = true;
       
        protected var _failurePolicy : String = AbstractWorkflowTask.ABORT;
       
        protected var _name : String = "unnamed";
       
        [ArrayElementType("it.aekidna.cleverplatypus.workflow.IWorkflowTask")]
        protected var _processedTasks : Array = []
       
        protected var _nextTaskIndex : int = 0;
       
        [ArrayElementType("it.aekidna.cleverplatypus.workflow.IWorkflowTask")]
        protected var _tasks : Array = [];

        protected var _parent : IWorkflowTask;
       
        public function Workflow() {
            super( this );
            try
            {
            	MetadataUtil.autowireObject( this );
            }
            catch( e : Error )
            {
            	LOGGER.warn( "Workflow {0} cannot be autowired because " + 
            			"no default context has been set for MetadataUtil", name );
            }
        }
       
        public function get name() : String
        {
            return _name;
        }
       
        public function set name( inName : String ) : void
        {
            _name = inName;
        }

       
       
        [Inspectable(defaultValue="serial", enumeration="parallel,serial")]
        public function get flow() : String
        {
            return _flow;
        }
       
        public function set flow( inFlow : String ) : void
        {
            if( inFlow != SERIAL && inFlow != PARALLEL )
            {
                throw new Error(
                    "Workflow 'flow' property can only be either 'parallel' or 'serial': " +
                    "found '" + inFlow + "'" );
            }
            _flow = inFlow;
        }
		
		[Bindable]
        [ArrayElementType("it.aekidna.cleverplatypus.workflow.IWorkflowTask")]
        public function get tasks() : Array
        {
            return _tasks;   
        }
        
        public function set tasks( inTasks : Array ) : void
        {
            _tasks = inTasks;
            if( _tasks != null )
            {
                for each( var task : IWorkflowTask in _tasks )
                {
                    task.parent = this;
                }
            }
        }

        public function get parent() : IWorkflowTask
        {
            return _parent;
        }
       
        public function set parent( inParent : IWorkflowTask ) : void
        {
            _parent = inParent;
        }

        [Inspectable(defaultValue="true", type="Boolean")]
        public function get enabled() : Boolean
        {
            return _enabled;
        }
       
        public function set enabled( inEnabled : Boolean ) : void
        {
            _enabled = inEnabled;
        }

        [Inspectable(defaultValue="abort", enumeration="abort,warn,ignore")]
        public function get failurePolicy() : String
        {
            return _failurePolicy;
        }
       
        public function set failurePolicy( inPolicy : String ) : void
        {
            if( inPolicy != AbstractWorkflowTask.ABORT &&
                inPolicy != AbstractWorkflowTask.IGNORE &&
                inPolicy != AbstractWorkflowTask.WARN )
            {
                throw new Error(
                    "Workflow 'failurePolicy' property can only be either 'abort' or 'warn' or 'ignore': " +
                    "found '" + inPolicy + "'" );
            }
            _failurePolicy = inPolicy;
        }
       
        public function begin() : void
        {
           _processedTasks = [];
            completedTasksCount = 0;
            _nextTaskIndex = 0;
            if( _tasks == null || _tasks.length == 0 )
            {
                LOGGER.warn( "Workflow {0} has no tasks to perform", _name );
                dispatchEvent( new WorkflowEvent( WorkflowEvent.COMPLETED ) );
                return;
            }
            for each( var task : IWorkflowTask in _tasks )
            {
                task.parent = this;
            }
            dispatchEvent( new WorkflowEvent( WorkflowEvent.STARTED ) );
            runNextTask();
        }
       
        protected function runNextTask() : void
        {
            if( _nextTaskIndex == _tasks.length )
            {
                return;
            }
            var task : IWorkflowTask = _tasks[ _nextTaskIndex ];
            _nextTaskIndex++;
            LOGGER.debug(
                "Next task '{0}'", task.name );
            if( task.enabled )
            {
                _processedTasks.push( task );
                addEventListeners( task );
                task.begin();
                task.dispatchEvent( new WorkflowEvent( WorkflowEvent.STARTED ) );
            } else {
                runNextTask();
                return;
            }
            if( _flow == PARALLEL )
            {
                runNextTask();
            }
        }
       
        protected function addEventListeners( inTask : IWorkflowTask ) : void
        {
            LOGGER.debug(
                "Registering listeners for '{0}'", inTask.name );
            inTask.addEventListener(
                WorkflowEvent.STARTED,
                onSubtaskStarted );
            inTask.addEventListener(
                WorkflowEvent.WARNING,
                onSubtaskWarning );
            inTask.addEventListener(
                WorkflowEvent.COMPLETED,
                onSubtaskCompleted );
            inTask.addEventListener(
                WorkflowEvent.FAULT,
                onSubtaskFault );
      }
     
        protected function removeEventListeners( inTask : IWorkflowTask ) : void
        {
            LOGGER.debug(
                "Unregistering listeners for '{0}'", inTask.name );
            inTask.removeEventListener(
                WorkflowEvent.STARTED,
                onSubtaskStarted );
            inTask.removeEventListener(
                WorkflowEvent.WARNING,
                onSubtaskWarning );
            inTask.removeEventListener(
                WorkflowEvent.COMPLETED,
                onSubtaskCompleted );
            inTask.removeEventListener(
                WorkflowEvent.FAULT,
                onSubtaskFault );
        }
     
        private function onSubtaskStarted( inEvent : WorkflowEvent ) : void
        {
            LOGGER.debug(
                "Task '{0}' started", IWorkflowTask( inEvent.target ).name );
        }

        private function onSubtaskWarning( inEvent : WorkflowEvent ) : void
        {
            onSubtaskCompleted( inEvent );
        }
       
        private function onSubtaskFault( inEvent : WorkflowEvent ) : void
        {
            if( IWorkflowTask (inEvent.target ).failurePolicy == AbstractWorkflowTask.ABORT )
            {
                if( _parent != null )
                {
                    dispatchEvent( inEvent );
                }
                else
                {
                    cleanUp();                   
                }
            }
            else
            {
                onSubtaskCompleted( inEvent );
            }
        }
       
        protected function onSubtaskCompleted( inEvent : WorkflowEvent ) : void
        {
        		completedTasksCount++;
            _processedTasks.splice( _processedTasks.indexOf( inEvent.target ), 1 );
            removeEventListeners( inEvent.target as IWorkflowTask );
            if( _flow == SERIAL && _nextTaskIndex < _tasks.length )
            {
                runNextTask();
                return;
            }
            else
            {
                if( _processedTasks.length == 0 )
                {
                    LOGGER.debug(
                        "Task '{0}' completed", name );
                    dispatchEvent( new WorkflowEvent( WorkflowEvent.COMPLETED ) );
                }
            }
        }
       
        public function cleanUp() : void
        {
            for each( var task : IWorkflowTask in _processedTasks )
            {
                removeEventListeners( task );
                task.cleanUp();
            }
            _processedTasks = [];
            completedTasksCount = 0;
            _nextTaskIndex = 0;
            if( _parent == null )
            {
                dispatchEvent( new WorkflowEvent( WorkflowEvent.FAULT ) );
            }
        }

    }
}